import { URL, format as urlFormat } from 'url';
import Joi from 'joi';
import Boom from '@hapi/boom';
import set from 'lodash/set';
import { path, pathOr } from 'ramda';
import { format as csvFormat } from '@fast-csv/format';
import debugCheck from '@kuiper-helpers/debug-check';
import nestedToOneLevelPath from '@kuiper-helpers/nested-to-one-level-path';
import pkg from '../../../package.json';
import isIP from '../../common/isIP';
import {
	JOB_TASK_TYPES,
	AVILIABLE_TASK_MAP,
	JOB_TASK_CATEGORY_KEY_LABEL_MAP,
	TASK_CATEGORY_TYPES_MAP,
	TASK_CATEGORY_KEYS,
} from '../../common/constants';
import { datetime as datetimeFormat } from '../../common/formatter';
import errorCodesBuilder from '../helpers/errorCodesBuilder';
import joiOmit from '../helpers/joiOmit';
import extractQueryFieldByJoi from '../helpers/extractQueryFieldByJoi';
import * as validation from '../../common/validation';
import { taskSettings } from '../../common/validation/common';
import joiToFilterArg from '../helpers/joiToFilterArg';
import createJob from '../graphql/Job/createOne';
import updateJob from '../graphql/Job/update';
import categorySecurityStatus from '../../common/categorySecurityStatus';
import findManyFilter from '../../common/validation/findManyFilter';
import { categorySecurityBuilder } from '../graphql/Job';
import escapeRegExp from '../helpers/escapeRegExp';

const { name } = pkg;
const errorCodes = errorCodesBuilder('资产监测处理失败');
const taskRespValidate = joiOmit(validation.taskWithId, {
	required: true,
	default: true,
}).allow(null);
let findManyFilterJob = findManyFilter.job.keys({
	pingSettings: joiOmit(taskSettings),
	httpSettings: joiOmit(taskSettings),
});
findManyFilterJob = findManyFilter.job.keys({
	OR: Joi.array().items(findManyFilterJob),
	AND: Joi.array().items(findManyFilterJob),
});

function getTasksUpdatetime(tasks) {
	let prevTypeUpdatetime;
	for (let i = tasks.length - 1; i >= 0; i--) {
		const { status, result } = tasks[i];
		const taskStatus = status?.status;
		if (taskStatus === 'active') {
			return '监测中...';
		}

		const lastUpdatetime = result?.endTime;
		if (!lastUpdatetime) continue;

		const updatetime = new Date(lastUpdatetime);

		if (!prevTypeUpdatetime) {
			prevTypeUpdatetime = updatetime;
		}

		if (prevTypeUpdatetime.getTime() < updatetime.getTime()) {
			prevTypeUpdatetime = updatetime;
		}
	}

	return prevTypeUpdatetime ? datetimeFormat(prevTypeUpdatetime) : '';
}

function fillJobCategoryStatus(item) {
	item.securityStatusCN = Object.entries(item.categoryStatus).reduce(
		(obj, [category, { status, warningCount }]) => {
			obj[category] = categorySecurityStatus({
				status,
				warningCount,
				category,
			});
			return obj;
		},
		{}
	);

	item.categoryUpdatetime = Object.entries(item.categoryStatus).reduce(
		(obj, [category, { updateTime }]) => {
			obj[category] = updateTime;
			return obj;
		},
		{}
	);

	item.updatetime = getTasksUpdatetime(
		Object.values(TASK_CATEGORY_TYPES_MAP)
			.reduce((a, b) => a.concat(b))
			.reduce((arr, taskType) => {
				if (AVILIABLE_TASK_MAP[taskType]) {
					(item[`${taskType}Status`] || []).forEach((_, idx) => {
						arr.push({
							status: path([`${taskType}Status`, idx], item),
							result: path([`${taskType}Result`, idx], item),
						});
					});
				} else {
					arr.push({
						status: item[`${taskType}Status`],
						result: item[`${taskType}Result`],
					});
				}
				return arr;
			}, [])
	);

	return item;
}

const omitEmptyFilter = filter => {
	return JSON.parse(JSON.stringify(filter), (key, value) => {
		if (typeof value !== 'object' || Array.isArray(value)) return value;
		if (Object.keys(value).length === 0) return undefined;
		return value;
	});
};

export const pagination = {
	tags: ['api', 'job'],
	description: '资产监测任务列表-分页查看',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		payload: Joi.object({
			page: Joi.number().default(1).description('当前页'),
			perPage: Joi.number().default(10).description('每页条目数'),
			filter: findManyFilterJob.keys({
				uidIn: Joi.array()
					.items(Joi.string())
					.description('使用用户ID列表过滤'),
				targetSearch: Joi.string().description('正则过滤 target.host'),
				abnormalStatus: Joi.string().description(
					'任务分组异常状态过滤。[任务组名，状态]'
				),
				assetType: Joi.string().allow('', null),
				noteSearch: Joi.string().allow('', null).description('资产名称'),
				createTimeRange: Joi.object({
					startTime: Joi.date().description('开始时间，大于等于'),
					endTime: Joi.date().description('结束时间，小于等于'),
				}).description('创建时间范围过滤'),
				uid: Joi.string().allow(null),
				_operators: Joi.object().unknown(),
			}),
		}),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					payload: { page, perPage, filter },
				} = request;
				console.log('job-pagination', { page, perPage, filter });
				return JSON.stringify({
					variables: { page, perPage, filter: omitEmptyFilter(filter) },
					// #region query
					query: `
						query($page: Int = 1, $perPage: Int = 10, $filter: FilterFindManyJobInput, $sort: SortFindManyJobInput = _ID_DESC) {
							JobPagination(page: $page, perPage: $perPage, filter: $filter, sort: $sort) {
								count
								pageInfo {
									currentPage
									perPage
									pageCount
									itemCount
									hasNextPage
									hasPreviousPage
								}
								items {
									_id
									targetUrl
									assetType
									target {
										protocol
										host
										hostname
										port
										ipType
										eTLD1
									}
									note
									defense_target_id
									uid
									assetId
									sourceIp
									cydEnabled
									enableMonitor
									enableAlert
									ignoredWarnIdMap
									sitePortraitId
									createTime
									verification {
										isVerified
										challenge
										verifyType
										verifiedTime
										note
									}
									prelogin{
										site_login
										login_method
										login_form_action_url
										login_form_postdata
										login_json_action_url
										login_json_postdata
										request_cookie
										request_http_headers
										is_http_auth_enabled
										http_auth_username
										http_auth_password
										is_proxy_enabled
										proxy_scheme
										proxy_host
										proxy_port
										proxy_username
										proxy_password
									}
									customMonitorPage {
										includeUrl
										excludeUrl
									}
									${JOB_TASK_TYPES.map(
										type => `
											${type}Settings {
												enable
												name
												sourceIp
												target
												collect
                        alert
                        enableSitePortraitTriggerMonitor
												interval
											}
										`
									).join('\n')}
									${JOB_TASK_TYPES.map(
										type => `
											${type}Status {
												_id
												status
												exception
												progress
												target
												taskType
												sourceIp
												startTime
												endTime
												securityStatus
												isSpecialTask
												triggerType
												taskSettings {
													enable
													name
													sourceIp
													target
													collect
													alert
													interval
												}
											}
										`
									).join('\n')}
									${JOB_TASK_TYPES.map(
										type => `
											${type}Result {
												_id
												target
												sourceIp
												startTime
												endTime
												securityStatus
												isSpecialTask
												triggerType
												taskType
												taskSettings {
													enable
													name
													sourceIp
													target
													collect
													alert
													interval
												}
												result {
													warnings {
														id
														title
														level
													}
													addition
												}
											}
										`
									).join('\n')}
									categoryStatus {
										${TASK_CATEGORY_KEYS.map(
											category => `
												${category} {
													status
													updateTime
													warningCount
												}
											`
										)}
									}
									alertSettings {
										alertGroupIds
										enable {
											asset
											vul
											cyd
											ssl
											securityEvent
											http
											ping
										}
										notification {
											email
											sms
											wechat
										}
										smsAllowedTime {
											start
											end
										}
										emailAllowedTime {
											start
											end
										}
										smsDailyLimit
										emailDailyLimit
									}
									user {
										_id
										username

										plan {
											_id
											name
										}
									}
								}
							}
						}`,
					// #endregion
				});
			},
			onResponse(response) {
				pathOr([], ['data', 'JobPagination', 'items'], response).forEach(
					fillJobCategoryStatus
				);
				return response;
			},
		},
	},
};

export const findMany = {
	tags: ['api', 'job'],
	description: '资产监测任务列表',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		payload: findManyFilterJob.keys({
			noteSearch: Joi.string().allow('', null).description('资产名称'),
			_operators: Joi.object().unknown(),
		}),
	},

	// response: {
	// 	schema: Joi.object({
	// 		data: Joi.object({
	// 			JobMany: Joi.array().items(validation.jobWithId),
	// 		}),
	// 	}).label('Response'),
	// },

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const { payload } = request;
				request.log.debug({ payload }, '请求资产监测列表');
				return JSON.stringify({
					variables: { filter: payload },
					// #region query
					query: `
						query($filter: FilterFindManyJobInput) {
							JobMany(filter: $filter, limit: 0) {
								_id
								targetUrl
								target {
									protocol
									host
									hostname
									port
									ipType
									eTLD1
								}
								note
								uid
								assetId
								sourceIp
								cydEnabled
								enableMonitor
								enableAlert
								assetType
								ignoredWarnIdMap
								assetType
								sitePortraitId
								prelogin{
									site_login
									login_method
									login_form_action_url
									login_form_postdata
									login_json_action_url
									login_json_postdata
									request_cookie
									request_http_headers
									is_http_auth_enabled
									http_auth_username
									http_auth_password
									is_proxy_enabled
									proxy_scheme
									proxy_host
									proxy_port
									proxy_username
									proxy_password
								}
								customMonitorPage {
									includeUrl
									excludeUrl
								}
								verification {
									isVerified
									challenge
									verifyType
									verifiedTime
									note
								}
								${JOB_TASK_TYPES.map(
									type => `
										${type}Settings {
											enable
											name
											sourceIp
											target
											collect
                      alert
                      enableSitePortraitTriggerMonitor
											interval
										}
									`
								).join('\n')}
								alertSettings {
									alertGroupIds
									enable {
										asset
										vul
										cyd
										ssl
										securityEvent
										http
										ping
									}
									notification {
										email
										sms
										wechat
									}
									smsAllowedTime {
										start
										end
									}
									emailAllowedTime {
										start
										end
									}
									smsDailyLimit
									emailDailyLimit
								}
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

export const findById = {
	tags: ['api', 'job'],
	description: '根据ID查询资产监测任务',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		params: Joi.object({
			jobId: Joi.string(),
		}),
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobById: validation.jobWithId.keys(
					JOB_TASK_TYPES.reduce(
						(obj, taskType) => {
							obj[`${taskType}Status`] = AVILIABLE_TASK_MAP[taskType]
								? Joi.array().items(taskRespValidate).default([])
								: taskRespValidate.allow(null);

							obj[`${taskType}Result`] = AVILIABLE_TASK_MAP[taskType]
								? Joi.array().items(taskRespValidate).default([])
								: taskRespValidate.allow(null);

							return obj;
						},
						{
							asset: joiOmit(validation.assetWithId, {
								default: true,
								required: true,
							}),
							_operators: Joi.object().unknown(),
						}
					)
				),
			}),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					params: { jobId },
				} = request;

				return JSON.stringify({
					variables: { _id: jobId },
					// #region query
					query: `
						query($_id: MongoID!) {
							JobById: JobOne(filter: { _id: $_id }) {
								_id
								targetUrl
								target {
									protocol
									host
									hostname
									port
									ipType
									eTLD1
								}
								note
								uid
								assetId
								sourceIp
								cydEnabled
								enableMonitor
								enableAlert
								ignoredWarnIdMap
								sitePortraitId
								prelogin{
									site_login
									login_method
									login_form_action_url
									login_form_postdata
									login_json_action_url
									login_json_postdata
									request_cookie
									request_http_headers
									is_http_auth_enabled
									http_auth_username
									http_auth_password
									is_proxy_enabled
									proxy_scheme
									proxy_host
									proxy_port
									proxy_username
									proxy_password
								}
								customMonitorPage {
									includeUrl
									excludeUrl
								}
								verification {
									isVerified
									challenge
									verifyType
									verifiedTime
									note
								}
								${JOB_TASK_TYPES.map(
									type => `
										${type}Settings {
											enable
											name
											sourceIp
											target
											collect
                      alert
                      enableSitePortraitTriggerMonitor
											interval
										}
									`
								).join('\n')}
								${JOB_TASK_TYPES.map(
									type => `
										${type}Status {
											_id
											status
											exception
											progress
											target
											taskType
											sourceIp
											startTime
											endTime
											securityStatus
											isSpecialTask
											triggerType
											taskSettings {
												enable
												name
												sourceIp
												target
												collect
												alert
												interval
											}
										}
									`
								).join('\n')}
								${JOB_TASK_TYPES.map(
									type => `
										${type}Result {
											_id
											target
											sourceIp
											startTime
											endTime
											securityStatus
											isSpecialTask
											triggerType
											taskType
											taskSettings {
												enable
												name
												sourceIp
												target
												collect
												alert
												interval
											}
											result {
												addition
												warnings {
													id
													title
													level
													affects
													isResolved
												}
											}
										}
									`
								).join('\n')}
								alertSettings {
									alertGroupIds
									enable {
										asset
										vul
										cyd
										ssl
										securityEvent
										http
										ping
									}
									notification {
										email
										sms
										wechat
									}
									smsAllowedTime {
										start
										end
									}
									emailAllowedTime {
										start
										end
									}
									smsDailyLimit
									emailDailyLimit
								}
								asset {
									_id
									taskSettings {
										enable
										name
										sourceIp
										target
										collect
										alert
										interval
									}
									alertSettings {
										alertGroupIds
										notification {
											email
											sms
											wechat
										}
									}
								}
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

export const updateById = {
	tags: ['api', 'job'],
	description: '更新资产监测任务',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		params: Joi.object({
			jobId: Joi.string().description('资产监测任务ID'),
		}),
		payload: joiOmit(validation.job),
		query: Joi.object({
			immediateExec: Joi.string().description('立即执行任务'),
		}).unknown(),
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobUpdateById: Joi.object({
					recordId: Joi.string().description('更新记录的ID'),
				}),
			}),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					payload,
					params: { jobId },
					query: { immediateExec },
				} = request;

				return JSON.stringify({
					variables: {
						immediateExec: immediateExec !== undefined,
						record: payload,
						filter: { _id: jobId },
					},
					// #region query
					query: `
						mutation($filter: FilterUpdateOneJobInput!, $record: UpdateOneJobInput!, $immediateExec: Boolean) {
							JobUpdateById: JobUpdateOne(filter: $filter, record: $record, immediateExec: $immediateExec) {
								recordId
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

export const removeById = {
	tags: ['api', 'job'],
	description: '删除任务集',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		params: Joi.object({
			jobId: Joi.string().required(),
		}),
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobRemoveById: Joi.object({
					recordId: Joi.string(),
				}).label('JobRemoveById'),
			}).label('Data'),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					params: { jobId },
				} = request;

				return JSON.stringify({
					variables: { jobId },
					// #region query
					query: `
						mutation($jobId: MongoID!) {
							JobRemoveById: JobRemoveOne(filter: { _id: $jobId }) {
								recordId
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

export const removeMany = {
	tags: ['api', 'job'],
	description: '批量删除任务集',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		payload: Joi.array()
			.items(Joi.string().description('任务集ID'))
			.required()
			.description('任务集ID列表'),
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobRemoveMany: Joi.object({
					numAffected: Joi.number().description('删除条数'),
				}).label('JobRemoveMany'),
			}).label('Data'),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const { payload } = request;

				return JSON.stringify({
					variables: { _ids: payload },
					// #region query
					query: `
						mutation($_ids: [MongoID]) {
							JobRemoveMany(filter: { _ids: $_ids }) {
								numAffected
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

export const count = {
	tags: ['api', 'job'],
	description: '资产监测任务数量统计',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		payload: Joi.object({
			filter: findManyFilterJob.keys({
				_operators: Joi.object().unknown(),
			}),
		}),
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobCount: Joi.number().description('数量'),
			}),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					payload: { filter },
				} = request;

				return JSON.stringify({
					variables: { filter },
					// #region query
					query: `
						query($filter: FilterCountJobInput) {
							JobCount(filter: $filter)
						}`,
					// #endregion
				});
			},
		},
	},
};

const createValidate = {
	subdomain: Joi.object({
		note: Joi.string()
			.max(50)
			.message('资产名称超过50字')
			.required()
			.messages({
				'string.base': '资产名称格式错误',
			})
			.description('备注'),
		target: Joi.string()
			.required()
			.custom((value, helpers) => {
				try {
					// eslint-disable-next-line no-new
					new URL(value);
				} catch (error) {
					return helpers.message('资产地址格式错误');
				}

				return value;
			})
			.messages({
				'string.empty': '资产地址格式错误',
			})
			.description('网站地址'),
		sourceIp: Joi.string()
			.allow('', null)
			.ip({ version: ['ipv4', 'ipv6'] })
			.message('源IP格式错误')
			.description('源IP'),
		username: Joi.alternatives()
			.try(
				Joi.number()
					.min(13000000000)
					.message('用户信息错误')
					.max(19999999999)
					.message('用户信息错误'),
				Joi.string().email().message('用户信息错误')
			)
			.required()
			.messages({
				'alternatives.match': '用户信息错误',
			}),
		enableMonitor: Joi.boolean().default(true).description('启用监测任务'),
		enableAlert: Joi.boolean().default(true).description('启用异常告警'),
		isVerified: Joi.boolean().default(false).description('是否验证'),
		immediateExec: Joi.boolean().default(false).description('立即下发'),
	}).description('子域名监测任务'),
	aviliableTask: Joi.object({
		name: Joi.string()
			.max(20)
			.message('任务名称不能超过20个字符')
			.required('任务名')
			.messages({
				'string.base': '任务名称不能为空',
			}),
		enable: Joi.boolean().default(true).description('启用监测任务'),
		target: Joi.string()
			.uri()
			.message('域名格式不正确')
			.description('监测目标'),
		sourceIp: Joi.string().allow('', null).description('监测URL'),
		username: Joi.string()
			.required('所属用户')
			.messages({
				'string.empty': '所属用户不能为空',
				'alternatives.match': '用户信息错误',
			})
			.description('所属用户'),
		taskType: Joi.string()
			.valid('http', 'ping')
			.required()
			.description('任务类型. ["http", "ping"]'),
		immediateExec: Joi.boolean().default(false).description('立即下发'),
	}).description('可用性监测任务'),
};

export const createBatchTask = {
	tags: ['api', 'job'],
	description: '批量创建资产监测任务',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },
	async handler(request) {
		const {
			payload: { aviliableTask: aviliableTaskList },
			server: {
				app: {
					model: { User, Job, Balance },
					config: {
						[name]: {
							__browser: { defaultJobSettings },
						},
					},
				},
			},
		} = request;

		const taskResult = [];

		const {
			httpSettings: [httpSettings],
			pingSettings: [pingSettings],
		} = JSON.parse(JSON.stringify(defaultJobSettings));

		let aviliableTask = [];
		await Promise.all(
			aviliableTaskList.map((item, index) =>
				createValidate.aviliableTask
					.validateAsync(item)
					.then(_ => {
						aviliableTask.push({
							...item,
							host: new URL(item.target).host,
						});
					})
					.catch(error => {
						taskResult.push({
							value: item.target,
							status: 'error',
							message: error.message,
						});
					})
			)
		);

		const usernames = Array.from(new Set(aviliableTask.map(x => x.username)));

		let usernameIdMap;
		const uidNameMap = {};
		try {
			usernameIdMap = await User.find(
				{ username: { $in: usernames } },
				{ _id: 1, username: 1 }
			).then(docs =>
				docs.reduce((obj, { _id, username }) => {
					obj[username] = _id;
					uidNameMap[_id] = username;
					return obj;
				}, {})
			);
		} catch (err) {
			const error = errorCodes['[03][批量创建任务]查询关联用户失败'].from(err);
			request.log.error({ error }, error.rawMessage);
			throw Boom.badRequest(error.message);
		}

		/**
		 * 已有任务提示已存在，不能覆盖或重复添加
		 * 只能添加已有资产的任务
		 */
		const savedJobs = [];
		const parseList = [];
		const oldJobMap = {};
		let jobHostMap;
		let balanceUidMap;
		try {
			let jobs = await Job.find({
				$or: aviliableTask.map(x => ({
					uid: usernameIdMap[x.username],
					'target.host': x.host,
				})),
			}).lean();

			jobs = jobs.reduce((total, item) => {
				if (!item.verification.isVerified) {
					taskResult.push({
						value: item.target.protocol + '//' + item.target.host,
						status: 'error',
						message: '当前可用性任务关联资产未认证',
					});
				} else {
					total.push(item);
				}
				return total;
			}, []);

			balanceUidMap = await Balance.find({
				uid: { $in: Array.from(new Set(jobs.map(x => x.uid))) },
			}).then(res =>
				res.reduce((total, item) => {
					total[item.uid] = item.balance.httpMaxTask.value;
					return total;
				}, {})
			);

			jobHostMap = jobs.reduce((obj, record) => {
				obj[record.target.host] = { record };
				// 套餐可用数量 + 已有可用性任务数 为当次可添加的可用性任务数
				balanceUidMap[record.uid] =
					balanceUidMap[record.uid] + record.httpSettings.length;
				oldJobMap[record.target.host] = record.httpSettings.map(item => {
					return item.target;
				});
				return obj;
			}, {});
		} catch (err) {
			const error = errorCodes[
				'[03][批量创建任务]可用性任务查询关联用户资产失败'
			].from(err);
			request.log.error({ error }, error.rawMessage);
			// 数据库报错直接抛出错误，后续都不执行
			throw Boom.badRequest(error.message, {
				code: error.code,
			});
		}

		const notFoundRelateJobs = aviliableTask.reduce((result, x) => {
			if (!jobHostMap[x.host]) {
				result[x.host] = x.host;
			}
			return result;
		}, {});

		if (
			Object.keys(jobHostMap).length &&
			Object.keys(notFoundRelateJobs).length
		) {
			const error = errorCodes[
				'[03][批量创建任务]未找到可用性监测任务关联的资产监测任务'
			].messageSuffix(': ' + Object.keys(notFoundRelateJobs).join('、'));
			request.log.warning(
				{ notFoundRelateJobs: Object.keys(notFoundRelateJobs) },
				error.rawMessage
			);
			aviliableTask.filter(item => {
				if (notFoundRelateJobs[item.host]) {
					taskResult.push({
						value: item.target,
						status: 'error',
						message: '未找到可用性监测任务关联的资产监测任务',
					});
				}
			});

			aviliableTask = aviliableTask.filter(item => {
				return jobHostMap[item.host];
			});
		}

		if (Object.keys(jobHostMap).length) {
			aviliableTask.forEach(
				({ name, enable, target, sourceIp, host, taskType }) => {
					const { record: job } = jobHostMap[host];
					const httpSettings_ = JSON.parse(JSON.stringify(httpSettings));
					console.log('[ host ]-1230', host);
					const ipNum = isIP(host);
					if (ipNum === 4) {
						httpSettings_.collect.area_ipv6 = [];
					} else if (ipNum === 6) {
						httpSettings_.collect.area = [];
					} else {
						const ipNum2 = isIP(sourceIp);
						if (ipNum2 === 4) {
							httpSettings_.collect.area_ipv6 = [];
						} else if (ipNum2 === 6) {
							httpSettings_.collect.area = [];
						} else {
							httpSettings_.collect.area_ipv6 = [];
						}
					}
					job[`${taskType}Settings`].push({
						...(taskType === 'http' ? httpSettings_ : pingSettings),
						name,
						enable,
						target,
						sourceIp,
					});
				}
			);
		}

		Object.values(jobHostMap)
			.reduce((total, item) => {
				item.filter = { _id: item.record._id };
				let httpSettings = Object.values(
					item.record.httpSettings.reduce((result, i) => {
						const target = new URL(i.target).href;
						if (result[target]) {
							taskResult.push({
								value: i.target,
								status: 'error',
								message: '当前可用性任务已添加',
							});
						} else {
							result[target] = i;
						}
						return result;
					}, {})
				);
				if (httpSettings.length > balanceUidMap[item.record.uid]) {
					const overLimit = httpSettings.slice(balanceUidMap[item.record.uid]);
					overLimit.map(i => {
						taskResult.push({
							value: i.target,
							status: 'error',
							message: uidNameMap[item.record.uid] + '可用性任务数已达套餐限制',
						});
					});
					httpSettings = httpSettings.slice(0, balanceUidMap[item.record.uid]);
				}
				item.record.httpSettings = httpSettings;
				item.record.pingSettings = Object.values(
					item.record.pingSettings.reduce((result, i) => {
						if (result[i.target]) {
							taskResult.push({
								value: i.target,
								status: 'error',
								message: '当前可用性任务已添加',
							});
						} else {
							result[i.target] = i;
						}
						return result;
					}, {})
				);
				total.push(item);
				return total;
			}, [])
			.forEach(args => {
				parseList.push(
					updateJob
						.resolve(null, args, { request })
						.then(x => {
							x.record.httpSettings.map(item => {
								// 当前成功任务不属于已有任务的则为新添加的任务
								if (
									oldJobMap[x.record.target.host].indexOf(item.target) === -1
								) {
									taskResult.unshift({
										value: item.target,
										status: 'success',
										message: '可用性任务添加成功',
									});
								}
							});
							return x.record._id.toString();
						})
						.then(x => {
							savedJobs.push(x);
							return x;
						})
						.catch(err => err)
				);
			});

		return Promise.all(parseList).then(result => {
			const dataNum = aviliableTaskList.length;
			const successNum = taskResult.filter(item => {
				return item.status === 'success';
			}).length;

			if (successNum < dataNum) {
				return {
					status: 'error',
					data: {
						data: taskResult,
						static: {
							total: dataNum,
							success: successNum,
							failed: dataNum - successNum,
						},
					},
				};
			} else if (successNum === dataNum) {
				return {
					message: `成功添加${successNum}条可用性任务，失败0条可用性任务`,
				};
			} else {
				return {
					message: `成功添加0条可用性任务，失败0条可用性任务`,
				};
			}
		});
	},
};

export const createMany = {
	tags: ['api', 'job'],
	description: '批量创建资产监测任务',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	async handler(request) {
		const {
			payload: { subdomain: subdomainList },
			server: {
				app: {
					model: { User },
					config: {
						[name]: {
							__browser: { defaultJobSettings },
						},
					},
				},
			},
		} = request;

		const domainResult = [];
		const subdomain = [];
		const userObj = {};
		for (let i = 0; i < subdomainList.length; i++) {
			if (userObj[subdomainList[i].username]) {
				userObj[subdomainList[i].username] += 1;
			} else {
				userObj[subdomainList[i].username] = 1;
			}
			const item = subdomainList[i];
			await createValidate.subdomain
				.validateAsync(item)
				.then(_ => {
					subdomain.push(item);
				})
				.catch(error => {
					domainResult.push({
						value: item.target,
						status: 'error',
						message: error.message,
					});
				});
		}

		const usernames = Array.from(new Set(subdomain.map(x => x.username)));
		let usernameIdMap;
		try {
			usernameIdMap = await User.find(
				{ username: { $in: usernames } },
				{ _id: 1, username: 1 }
			).then(docs =>
				docs.reduce((obj, { _id, username }) => {
					obj[username] = _id;
					return obj;
				}, {})
			);
		} catch (err) {
			const error = errorCodes['[03][批量创建任务]查询关联用户失败'].from(err);
			request.log.error({ error }, error.rawMessage);
			throw Boom.badRequest(error.message);
		}
		const jobs = subdomain.map(
			({
				username,
				target,
				sourceIp,
				note,
				enableMonitor,
				enableAlert,
				isVerified,
				immediateExec: jobImmediateExec,
			}) => {
				const immediateExec = jobImmediateExec;

				let host;
				let protocol;
				try {
					const url = new URL(target);
					host = url.host;
					protocol = url.protocol;
				} catch (err) {
					throw Boom.badRequest(`无效的域名 (${target})`);
				}
				if (!usernameIdMap[username]) {
					throw Boom.badRequest(`没有找到对应的用户名 (${username})`);
				}
				const defaultJob_ = JSON.parse(JSON.stringify(defaultJobSettings));
				const ipNum = isIP(host);
				if (ipNum === 4) {
					defaultJob_.pingSettings[0].collect.area_ipv6 = [];
					defaultJob_.httpSettings[0].collect.area_ipv6 = [];
				} else if (ipNum === 6) {
					defaultJob_.pingSettings[0].collect.area = [];
					defaultJob_.httpSettings[0].collect.area = [];
				} else {
					const ipNum2 = isIP(sourceIp);
					if (ipNum2 === 4) {
						defaultJob_.pingSettings[0].collect.area_ipv6 = [];
						defaultJob_.httpSettings[0].collect.area_ipv6 = [];
					} else if (ipNum2 === 6) {
						defaultJob_.pingSettings[0].collect.area = [];
						defaultJob_.httpSettings[0].collect.area = [];
					} else {
						defaultJob_.pingSettings[0].collect.area_ipv6 = [];
						defaultJob_.httpSettings[0].collect.area_ipv6 = [];
					}
				}
				const job = {
					...defaultJob_,
					sourceIp,
					note,
					enableMonitor,
					enableAlert,
					target: { host, protocol },
					uid: usernameIdMap[username] + '',
					verification: {
						isVerified,
						verifyType: 'oms',
					},
				};
				try {
					return {
						immediateExec,
						record: validation.job.validate(job).value,
					};
				} catch (err) {
					const error = errorCodes['[03][批量创建任务]传入参数校验失败'].from(
						err
					);
					request.log.error({ error }, error.rawMessage);
					throw Boom.badRequest(error.detail, {
						path: err.path,
						detail: err.message,
					});
				}
			}
		);
		const resultJob = {};
		const savedJobs = [];
		const parseList = () => {
			return jobs.reduce((total, item) => {
				const target =
					item.record.target.protocol + '//' + item.record.target.host;
				if (resultJob[target]) {
					domainResult.push({
						value: target,
						status: 'error',
						message: '当前域名已添加',
					});
				} else {
					resultJob[target] = item;
					total.push(item);
				}
				return total;
			}, []);
		};
		const jobsTotal = parseList();
		let itemNum = 0;
		for (let index = 0; index < jobsTotal.length; index++) {
			const args = jobsTotal[index];
			const item = await createJob
				.resolve(null, args, { request })
				.catch(err => {
					const { reason: message, host: value } = err.addition || {};
					if (value) {
						domainResult.push({
							value,
							status: 'error',
							message,
						});
					} else {
						domainResult.push({
							value:
								args.record.target.protocol + '//' + args.record.target.host,
							status: 'error',
							message:
								err.message.indexOf('未购买套餐') > -1
									? '当前用户未购买套餐'
									: err.message,
						});
					}
					return 'err';
				});
			if (item !== 'err') {
				domainResult.unshift({
					value: item.record.target.protocol + '//' + item.record.target.host,
					status: 'success',
					message: '域名资产添加成功',
				});
				const itemX = item.record._id.toString();
				savedJobs.push(itemX);
				itemNum += 1;
			}
		}
		const dataNum = subdomainList.length;
		if (itemNum < dataNum) {
			return {
				status: 'error',
				data: {
					data: domainResult,
					static: {
						total: dataNum,
						success: itemNum,
						failed: dataNum - itemNum,
					},
				},
			};
		} else if (itemNum === dataNum) {
			return {
				message: `成功更新${itemNum}条资产，失败0条资产`,
			};
		} else {
			return {
				message: `成功更新0条资产，失败0条资产`,
			};
		}
	},
};

export const findJobHostnameList = {
	tags: ['api', 'job'],
	description: '资产监测任务 Hostname 列表',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		payload: findManyFilterJob,
	},

	response: {
		schema: Joi.object({
			data: Joi.object({
				JobMany: Joi.array().items(
					Joi.object({
						_id: Joi.string().required().description('监测任务ID'),
						target: Joi.object({
							hostname: Joi.string().required().description('主机名'),
						}),
						uid: Joi.string().description('用户ID'),
						sourceIp: Joi.string().allow(null, '').description('目标源IP'),
					})
				),
			}),
		}).label('Response'),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const { payload } = request;
				request.log.debug({ payload }, '请求资产监测列表');

				return JSON.stringify({
					variables: { filter: payload },
					// #region query
					query: `
						query($filter: FilterFindManyJobInput) {
							JobMany(filter: $filter, limit: 0) {
								_id
								targetUrl
								target {
									hostname
								}
								uid
								sourceIp
							}
						}`,
					// #endregion
				});
			},
		},
	},
};

const jobCsvHeader = [
	{ name: '资产名称', fieldPath: ['note'] },
	{
		name: '资产地址',
		fieldPath: ['target'],
		render: target => urlFormat(target),
	},
	{
		name: '认证状态',
		fieldPath: ['verification', 'isVerified'],
		render: isVerified => (isVerified ? '已认证' : '未认证'),
	},
	{
		name: '安全状态',
		fieldPath: ['securityStatusCN'],
		render: item =>
			Object.entries({ ...JOB_TASK_CATEGORY_KEY_LABEL_MAP, asset: '资产变动' })
				.map(([key, label]) => `${label}: ${item[key] || ''}`)
				.join('\n'),
	},
	{
		name: '最新监测完成时间',
		fieldPath: ['categoryStatus'],
		render: cate =>
			Object.entries({
				...JOB_TASK_CATEGORY_KEY_LABEL_MAP,
				asset: '资产变动',
			})
				.map(
					([key, label]) =>
						`${label}: ${datetimeFormat(cate[key].updateTime) || '- -'}`
				)
				.join('\n'),
	},
	{
		name: '监测状态',
		fieldPath: ['enableMonitor'],
		render: x => (x ? '启用' : '关闭'),
	},
	{
		name: '告警状态',
		fieldPath: ['enableAlert'],
		render: x => (x ? '启用' : '关闭'),
	},
	{ name: '所属用户', fieldPath: ['uid', 'username'] },
	{ name: '套餐', fieldPath: ['uid', 'planId', 'name'] },
	{ name: '创建时间', fieldPath: ['createTime'], render: datetimeFormat },
];
export const download = {
	tags: ['api', 'job'],
	description: '下载监测任务列表',
	auth: {
		mode: 'required',
		...(process.env.NODE_ENV === 'development'
			? {}
			: {
					access: {
						// 只允许使用 URL Token 访问
						entity: 'app',
					},
			  }),
	},

	timeout: { socket: false },

	validate: {
		query: joiToFilterArg(joiOmit(validation.jobWithId)).unknown(),
	},

	handler(request, h) {
		const {
			query,
			server: {
				app: {
					model: { Job },
				},
			},
			auth: {
				credentials: { uid, permissionMap },
			},
		} = request;

		const filterOps = JSON.parse(query.filter || '{}');

		const filter = query.filter
			? nestedToOneLevelPath(
					extractQueryFieldByJoi({
						query: filterOps,
						schema: joiToFilterArg(joiOmit(validation.jobWithId)).unknown(),
					})
			  )
			: {};

		if (filter.createTimeGTE) {
			set(filter, ['createTime', '$gte'], filter.createTimeGTE);
			delete filter.createTimeGTE;
		}
		if (filter.createTimeLTE) {
			set(filter, ['createTime', '$lte'], filter.createTimeLTE);
			delete filter.createTimeLTE;
		}
		if (filter['target.hostname']) {
			set(filter, ['target.hostname'], {
				$regex: escapeRegExp(filter['target.hostname'].trim()),
			});
		}
		if (!permissionMap['cloud-monitor\x01listJob\x010']) {
			set(filter, ['uid'], uid);
		}

		const stream = csvFormat({
			writeBOM: true,
			headers: jobCsvHeader.map(x => x.name),
		}).transform(data => {
			try {
				const row = fillJobCategoryStatus(data);
				return jobCsvHeader.reduce((obj, { name, fieldPath, render }) => {
					obj[name] = pathOr('', fieldPath, row);
					if (render) obj[name] = render(obj[name]);
					return obj;
				}, {});
			} catch (error) {
				request.log.error({ error }, '资产列表下载：行数据处理失败');
				return {};
			}
		});

		setTimeout(async () => {
			try {
				const queryData = Job.find(filter)
					.sort({ _id: -1 })
					.populate(
						JOB_TASK_TYPES.reduce((arr, x) => {
							arr.push(`${x}StatusId`, `${x}ResultId`);
							return arr;
						}, [])
					)
					.populate({
						path: 'uid',
						populate: { path: 'planId' },
					})
					.populate({
						path: 'assetId',
						populate: { path: 'resultId' },
					});
				for await (const doc of queryData) {
					let tasks = [];
					JOB_TASK_TYPES.forEach(key => {
						doc[`${key}Status`] = doc[`${key}StatusId`];
						doc[`${key}Result`] = doc[`${key}ResultId`];
						tasks = tasks.concat(doc[`${key}Result`]).filter(x => x);
					});
					doc.categoryStatus = categorySecurityBuilder({
						tasks,
						record: doc,
						asset: doc.assetId,
					});
					stream.write(doc);
				}
			} catch (err) {
				const error = errorCodes['[03]任务配置列表下载失败'].from(err);
				request.log.error({ error }, error.rawMessage);
			} finally {
				stream.end();
			}
		}, 10);

		return h
			.response(stream)
			.header('Content-Type', 'text/csv')
			.header('Content-Disposition', 'attachment; filename="job.csv"');
	},
};
export const countTask = {
	tags: ['api', 'job'],
	description: '监测任务统计',
	auth: {
		mode: 'required',
		...(process.env.NODE_ENV === 'development'
			? {}
			: {
					access: {
						// 只允许使用 URL Token 访问
						entity: 'app',
					},
			  }),
	},

	timeout: { socket: false },

	validate: {
		payload: Joi.object(),
	},

	handler(request, h) {
		const {
			payload: { task, enable },
			server: {
				app: {
					model: { Job },
				},
			},
		} = request;
		const params = [
			{ $project: { count: { $add: 1 } } },
			{
				$group: {
					_id: null,
					count: { $sum: '$count' },
				},
			},
		];
		params.unshift({ $unwind: `$${task}` });
		if (enable) {
			const objEnable = { $match: {} };
			objEnable.$match[task] = { $elemMatch: { enable: true } };
			params.unshift(objEnable);
		}
		const queryData = Job.aggregate(params);
		return queryData;
	},
};
export const jobAviliableTaskNameList = {
	tags: ['api', 'job'],
	description: '资产监测任务 Hostname 列表',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		query: Joi.object({
			taskType: Joi.string().default('http').description('任务类型'),
		}).unknown(),
	},

	response: {
		schema: Joi.array().items(
			Joi.object({
				_id: Joi.string().required().description('监测任务ID'),
				domain: Joi.string().description('域名'),
				name: Joi.string().description('任务名'),
			})
		),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					query: { taskType },
				} = request;
				request.log.debug({ taskType }, '请求资产监测列表');

				return JSON.stringify({
					variables: { filter: {} },
					// #region query
					query: `
						query($filter: FilterFindManyJobInput) {
							JobMany(filter: $filter, limit: 0) {
								_id
								targetUrl
								target {
									hostname
								}
								${taskType === 'http' ? 'httpSettings' : 'pingSettings'} {
									name
									target
								}
							}
						}`,
					// #endregion
				});
			},
			onResponse(resp) {
				return (resp?.data?.JobMany || []).reduce((arr, job) => {
					['pingSettings', 'httpSettings'].forEach(key => {
						if (!job[key]) return;
						// eslint-disable-next-line no-unused-expressions
						job[key]?.forEach(x => {
							arr.push({
								_id: job._id,
								domain: job.target.hostname,
								name: x.name,
							});
						});
					});

					return arr;
				}, []);
			},
		},
	},
};

export const verification = {
	tags: ['api', 'job'],
	description: '修改资产验证状态',
	auth: debugCheck('disable-open-api-auth')
		? false
		: {
				mode: 'required',
				access: {
					// 只允许使用 URL Token 访问
					entity: 'app',
				},
		  },

	validate: {
		params: Joi.object({
			jobId: Joi.string().description('资产ID'),
		}),
		payload: Joi.object({
			isVerified: Joi.boolean()
				.default(false)
				.description('是否通过网站所有权验证'),
			note: Joi.string().allow('').description('备注'),
			uid: Joi.string().description('用户ID'),
		}),
	},

	handler: {
		injectGraphql: {
			payloadBuilder(request) {
				const {
					query: { taskType },
					params: { jobId },
					payload: { isVerified, note, uid },
				} = request;
				request.log.debug({ taskType }, '请求资产监测列表');

				return JSON.stringify({
					variables: {
						filter: {
							uid,
							_id: jobId,
						},
						record: {
							verification: { isVerified, note },
						},
					},
					// #region query
					query: `
						mutation($filter: FilterUpdateOneJobInput!, $record: UpdateOneJobInput) {
							JobOwnershipVerify(filter: $filter, record: $record) {
								_id
								verification {
									isVerified
								}
							}
						}`,
					// #endregion
				});
			},
			onResponse(resp) {
				return { message: '修改成功' };
			},
		},
	},
};
